home *** CD-ROM | disk | FTP | other *** search
/ Tech Arsenal 1 / Tech Arsenal (Arsenal Computer).ISO / tek-13 / xvisrc.zip / CMDLINE.C < prev    next >
C/C++ Source or Header  |  1992-07-28  |  27KB  |  1,256 lines

  1. /* Copyright (c) 1990,1991,1992 Chris and John Downey */
  2. #ifndef lint
  3. static char *sccsid = "@(#)cmdline.c    2.2 (Chris & John Downey) 8/6/92";
  4. #endif
  5.  
  6. /***
  7.  
  8. * program name:
  9.     xvi
  10. * function:
  11.     PD version of UNIX "vi" editor, with extensions.
  12. * module name:
  13.     cmdline.c
  14. * module function:
  15.     Command-line handling (i.e. :/? commands) - most
  16.     of the actual command functions are in ex_cmds.c.
  17. * history:
  18.     STEVIE - ST Editor for VI Enthusiasts, Version 3.10
  19.     Originally by Tim Thompson (twitch!tjt)
  20.     Extensive modifications by Tony Andrews    (onecom!wldrdg!tony)
  21.     Heavily modified by Chris & John Downey
  22.  
  23. ***/
  24.  
  25. #include "xvi.h"
  26.  
  27. #ifdef    MEGAMAX
  28. overlay "cmdline"
  29. #endif
  30.  
  31. /*
  32.  * The next two variables contain the bounds of any range
  33.  * given in a command. If no range was given, both will be NULL.
  34.  * If only a single line was given, u_line will be NULL.
  35.  * The a_line variable is used for those commands which take
  36.  * a third line specifier after the command, e.g. "move", "copy".
  37.  */
  38. static    Line    *l_line, *u_line;
  39. static    Line    *a_line;
  40.  
  41. /*
  42.  * Definitions for all ex commands.
  43.  */
  44.  
  45. #define    EX_ENOTFOUND    -1        /* command not found */
  46. #define    EX_EAMBIGUOUS    -2        /* could be more than one */
  47. #define    EX_ECANTFORCE    -3        /* ! given where not appropriate */
  48. #define    EX_EBADARGS    -4        /* inappropriate args given */
  49.  
  50. #define    EX_NOCMD    1
  51. #define    EX_SHCMD    2
  52. #define    EX_UNUSED    3    /* unused */
  53. #define    EX_AMPERSAND    4
  54. #define    EX_EXBUFFER    5
  55. #define    EX_LSHIFT    6
  56. #define    EX_EQUALS    7
  57. #define    EX_RSHIFT    8
  58. #define    EX_COMMENT    9
  59. #define    EX_ABBREVIATE    10
  60. #define    EX_APPEND    11
  61. #define    EX_ARGS        12
  62. #define    EX_BUFFER    13
  63. #define    EX_CHDIR    14
  64. #define    EX_CHANGE    15
  65. #define    EX_CLOSE    16
  66. #define    EX_COMPARE    17
  67. #define    EX_COPY        18
  68. #define    EX_DELETE    19
  69. #define    EX_ECHO        20
  70. #define    EX_EDIT        21
  71. #define    EX_EX        22
  72. #define    EX_FILE        23
  73. #define    EX_GLOBAL    24
  74. #define    EX_HELP        25
  75. #define    EX_INSERT    26
  76. #define    EX_JOIN        27
  77. #define    EX_K        28
  78. #define    EX_LIST        29
  79. #define    EX_MAP        30
  80. #define    EX_MARK        31
  81. #define    EX_MOVE        32
  82. #define    EX_NEXT        33
  83. #define    EX_NUMBER    34
  84. #define    EX_OPEN        35
  85. #define    EX_PRESERVE    36
  86. #define    EX_PRINT    37
  87. #define    EX_PUT        38
  88. #define    EX_QUIT        39
  89. #define    EX_READ        40
  90. #define    EX_RECOVER    41
  91. #define    EX_REWIND    42
  92. #define    EX_SET        43
  93. #define    EX_SHELL    44
  94. #define    EX_SOURCE    45
  95. #define    EX_SPLIT    46
  96. #define    EX_SUSPEND    47
  97. #define    EX_SUBSTITUTE    48
  98. #define    EX_TAG        49
  99. #define    EX_UNABBREV    50
  100. #define    EX_UNDO        51
  101. #define    EX_UNMAP    52
  102. #define    EX_V        53
  103. #define    EX_VERSION    54
  104. #define    EX_VISUAL    55
  105. #define    EX_WN        56
  106. #define    EX_WQ        57
  107. #define    EX_WRITE    58
  108. #define    EX_XIT        59
  109. #define    EX_YANK        60
  110. #define    EX_Z        61
  111. #define    EX_GOTO        62
  112. #define    EX_TILDE    63
  113.  
  114. /*
  115.  * Table of all ex commands, and whether they take an '!'.
  116.  *
  117.  * Note that this table is in strict order, sorted on
  118.  * the ASCII value of the first character of the command.
  119.  *
  120.  * The priority field is necessary to resolve clashes in
  121.  * the first one or two characters; so each group of commands
  122.  * beginning with the same letter should have at least one
  123.  * priority 1, so that there is a sensible default.
  124.  *
  125.  * Commands with argument type ec_rest need no delimiters;
  126.  * they need only be matched. This is really only used for
  127.  * single-character commands like !, " and &.
  128.  */
  129. static    struct    ecmd    {
  130.     char    *ec_name;
  131.     short    ec_command;
  132.     short    ec_priority;
  133.     unsigned    ec_flags;
  134.     /*
  135.      * Flags: EXCLAM means can use !, FILEXP means do filename
  136.      * expansion, INTEXP means do % and # expansion. EXPALL means
  137.      * do INTEXP and FILEXP (they are done in that order).
  138.      *
  139.      * EC_RANGE0 means that the range specifier (if any)
  140.      * may include line 0.
  141.      */
  142. #   define    EC_EXCLAM    0x1
  143. #   define    EC_FILEXP    0x2
  144. #   define    EC_INTEXP    0x4
  145. #   define    EC_EXPALL    EC_FILEXP|EC_INTEXP
  146. #   define    EC_RANGE0    0x8
  147.  
  148.     enum {
  149.     ec_none,        /* no arguments after command */
  150.     ec_strings,        /* whitespace-separated strings */
  151.     ec_1string,        /* like ec_strings but only one */
  152.     ec_line,        /* line number or target argument */
  153.     ec_rest,        /* rest of line passed entirely */
  154.     ec_nonalnum,        /* non-alphanumeric delimiter */
  155.     ec_1lower        /* single lower-case letter */
  156.     }    ec_arg_type;
  157. } cmdtable[] = {
  158. /*  name        command         priority    exclam    */
  159.  
  160.     /*
  161.      * The zero-length string is used for the :linenumber command.
  162.      */
  163.     "",            EX_NOCMD,        1,    EC_RANGE0,        ec_none,
  164.     "!",        EX_SHCMD,        0,    EC_INTEXP,        ec_rest,
  165.  
  166.     "#",        EX_NUMBER,        0,    0,            ec_none,
  167.     "&",        EX_AMPERSAND,   0,    0,            ec_rest,
  168.     "*",        EX_EXBUFFER,    0,    0,            ec_rest,
  169.     "<",        EX_LSHIFT,        0,    0,            ec_none,
  170.     "=",        EX_EQUALS,        0,    0,            ec_none,
  171.     ">",        EX_RSHIFT,        0,    0,            ec_none,
  172.     "@",        EX_EXBUFFER,    0,    0,            ec_rest,
  173.     "\"",        EX_COMMENT,        0,    0,            ec_rest,
  174.  
  175.     "abbreviate",   EX_ABBREVIATE,  0,    0,            ec_strings,
  176.     "append",        EX_APPEND,        1,    0,            ec_none,
  177.     "args",        EX_ARGS,        0,    0,            ec_none,
  178.  
  179.     "buffer",        EX_BUFFER,        0,    EC_EXPALL,        ec_1string,
  180.  
  181.     "cd",        EX_CHDIR,        1,    EC_EXPALL,        ec_1string,
  182.     "change",        EX_CHANGE,        2,    0,            ec_none,
  183.     "chdir",        EX_CHDIR,        1,    EC_EXPALL,        ec_1string,
  184.     "close",        EX_CLOSE,        1,    EC_EXCLAM,        ec_none,
  185.     "compare",        EX_COMPARE,        0,    0,            ec_none,
  186.     "copy",        EX_COPY,        1,    0,            ec_line,
  187.  
  188.     "delete",        EX_DELETE,        0,    0,            ec_none,
  189.  
  190.     "echo",        EX_ECHO,        0,    EC_INTEXP,        ec_strings,
  191.     "edit",        EX_EDIT,        1,    EC_EXCLAM|EC_EXPALL,    ec_1string,
  192.     "ex",        EX_EX,        0,    EC_EXPALL,        ec_1string,
  193.  
  194.     "file",        EX_FILE,        0,    EC_EXPALL,        ec_1string,
  195.  
  196.     "global",        EX_GLOBAL,        0,    EC_EXCLAM,        ec_nonalnum,
  197.  
  198.     "help",        EX_HELP,        0,    0,            ec_none,
  199.  
  200.     "insert",        EX_INSERT,        0,    0,            ec_none,
  201.  
  202.     "join",        EX_JOIN,        0,    0,            ec_none,
  203.  
  204.     "k",        EX_K,        0,    0,            ec_1lower,
  205.  
  206.     "list",        EX_LIST,        0,    0,            ec_none,
  207.  
  208.     "map",        EX_MAP,        0,    EC_EXCLAM,        ec_strings,
  209.     "mark",        EX_MARK,        0,    0,            ec_1lower,
  210.     "move",        EX_MOVE,        1,    0,            ec_line,
  211.  
  212.     "next",        EX_NEXT,        1,    EC_EXCLAM|EC_EXPALL,    ec_strings,
  213.     "number",        EX_NUMBER,        0,    0,            ec_none,
  214.  
  215.     "open",        EX_OPEN,        0,    0,            ec_none,
  216.  
  217.     "preserve",        EX_PRESERVE,    0,    0,            ec_none,
  218.     "print",        EX_PRINT,        1,    0,            ec_none,
  219.     "put",        EX_PUT,        0,    EC_RANGE0,        ec_none,
  220.  
  221.     "quit",        EX_QUIT,        0,    EC_EXCLAM,        ec_none,
  222.  
  223.     "read",        EX_READ,        1,    EC_EXPALL|EC_RANGE0,    ec_1string,
  224.     "recover",        EX_RECOVER,        0,    0,            ec_none,
  225.     "rewind",        EX_REWIND,        0,    EC_EXCLAM,        ec_none,
  226.  
  227.     "set",        EX_SET,        0,    0,            ec_strings,
  228.     "shell",        EX_SHELL,        0,    0,            ec_none,
  229.     "source",        EX_SOURCE,        0,    EC_EXPALL,        ec_1string,
  230.     "split",        EX_SPLIT,        0,    0,            ec_none,
  231.     "stop",        EX_SUSPEND,        0,    0,            ec_none,
  232.     "substitute",   EX_SUBSTITUTE,  1,    0,            ec_nonalnum,
  233.     "suspend",        EX_SUSPEND,        0,    0,            ec_none,
  234.  
  235.     "t",        EX_COPY,        1,    0,            ec_line,
  236.     "tag",        EX_TAG,        0,    EC_EXCLAM,        ec_1string,
  237.  
  238.     "unabbreviate", EX_UNABBREV,    0,    0,            ec_strings,
  239.     "undo",        EX_UNDO,        1,    0,            ec_none,
  240.     "unmap",        EX_UNMAP,        0,    EC_EXCLAM,        ec_strings,
  241.  
  242.     "v",        EX_V,        1,    0,            ec_nonalnum,
  243.     "version",        EX_VERSION,        0,    0,            ec_none,
  244.     "visual",        EX_VISUAL,        0,    EC_EXCLAM|EC_EXPALL,    ec_1string,
  245.  
  246.     "wn",        EX_WN,        0,    EC_EXCLAM,        ec_none,
  247.     "wq",        EX_WQ,        0,    EC_EXCLAM|EC_EXPALL,    ec_1string,
  248.     "write",        EX_WRITE,        1,    EC_EXCLAM|EC_EXPALL,    ec_1string,
  249.  
  250.     "xit",        EX_XIT,        0,    0,            ec_none,
  251.  
  252.     "yank",        EX_YANK,        0,    0,            ec_none,
  253.  
  254.     "z",        EX_Z,        0,    0,            ec_none,
  255.  
  256.     "|",        EX_GOTO,        0,    0,            ec_none,
  257.     "~",        EX_TILDE,        0,    0,            ec_rest,
  258.  
  259.     NULL,        0,            0,    0,            ec_none,
  260. };
  261.  
  262. /*
  263.  * Internal routine declarations.
  264.  */
  265. static    int    decode_command P((char **, bool_t *, struct ecmd **));
  266. static    bool_t    get_line P((char **, Line **));
  267. static    bool_t    get_range P((char **));
  268. static    void    badcmd P((bool_t, char *));
  269. static    char    *show_line P((void));
  270. static    char    *expand_percents P((char *));
  271.  
  272. /*
  273.  * These are used for display mode.
  274.  */
  275. static    Line    *curline;
  276. static    Line    *lastline;
  277. static    bool_t    do_line_numbers;
  278.  
  279. /*
  280.  * Macro to skip over whitespace during command line interpretation.
  281.  */
  282. #define skipblanks(p)    { while (*(p) != '\0' && is_space(*(p))) (p)++; }
  283.  
  284. /*
  285.  * do_colon() - process a ':' command.
  286.  *
  287.  * The cmdline argument points to a complete command line to be processed
  288.  * (this does not include the ':' itself).
  289.  */
  290. void
  291. do_colon(cmdline, interactive)
  292. char    *cmdline;            /* optional command string */
  293. bool_t    interactive;            /* true if reading from tty */
  294. {
  295.     char    *arg;            /* ptr to string arg(s) */
  296.     int        argc = 0;        /* arg count for ec_strings */
  297.     char    **argv = NULL;        /* arg vector for ec_strings */
  298.     bool_t    exclam;            /* true if ! was given */
  299.     int        command;        /* which command it is */
  300.     struct ecmd    *ecp;            /* ptr to command entry */
  301.     unsigned    savecho;        /* previous value of echo */
  302.  
  303.     /*
  304.      * Clear the range variables.
  305.      */
  306.     l_line = NULL;
  307.     u_line = NULL;
  308.  
  309.     /*
  310.      * Parse a range, if present (and update the cmdline pointer).
  311.      */
  312.     if (!get_range(&cmdline)) {
  313.     return;
  314.     }
  315.  
  316.     /*
  317.      * Decode the command.
  318.      */
  319.     skipblanks(cmdline);
  320.     command = decode_command(&cmdline, &exclam, &ecp);
  321.  
  322.     if (command > 0) {
  323.     /*
  324.      * Check that the range specified,
  325.      * if any, is legal for the command.
  326.      */
  327.     if (!(ecp->ec_flags & EC_RANGE0)) {
  328.         if (l_line == curbuf->b_line0 || u_line == curbuf->b_line0) {
  329.         show_error(curwin,
  330.             "Specification of line 0 not allowed");
  331.         return;
  332.         }
  333.     }
  334.  
  335.     switch (ecp->ec_arg_type) {
  336.     case ec_none:
  337.         if (*cmdline != '\0' &&
  338.             (*cmdline != '!' || !(ecp->ec_flags & EC_EXCLAM))) {
  339.         command = EX_EBADARGS;
  340.         }
  341.         break;
  342.  
  343.     case ec_line:
  344.         a_line = NULL;
  345.         skipblanks(cmdline);
  346.         if (!get_line(&cmdline, &a_line) || a_line == NULL) {
  347.         command = EX_EBADARGS;
  348.         }
  349.         break;
  350.  
  351.     case ec_1lower:
  352.         /*
  353.          * One lower-case letter.
  354.          */
  355.         skipblanks(cmdline);
  356.         if (!is_lower(cmdline[0]) || cmdline[1] != '\0') {
  357.         command = EX_EBADARGS;
  358.         } else {
  359.         arg = cmdline;
  360.         }
  361.         break;
  362.  
  363.     case ec_nonalnum:
  364.     case ec_rest:
  365.     case ec_strings:
  366.     case ec_1string:
  367.         arg = cmdline;
  368.         if (ecp->ec_arg_type == ec_strings ||
  369.                     ecp->ec_arg_type == ec_1string) {
  370.         if (*arg == '\0') {
  371.             /*
  372.              * No args.
  373.              */
  374.             arg = NULL;
  375.         } else {
  376.             /*
  377.              * Null-terminate the command and skip
  378.              * whitespace to arg or end of line.
  379.              */
  380.             *arg++ = '\0';
  381.             skipblanks(arg);
  382.  
  383.             /*
  384.              * There was trailing whitespace,
  385.              * but no args.
  386.              */
  387.             if (*arg == '\0') {
  388.             arg = NULL;
  389.             }
  390.         }
  391.         } else if (ecp->ec_arg_type == ec_nonalnum && exclam) {
  392.         /*
  393.          * We don't normally touch the arguments for
  394.          * this type, but we have to null-terminate
  395.          * the '!' at least.
  396.          */
  397.         *arg++ = '\0';
  398.         }
  399.  
  400.         if (arg != NULL) {
  401.         /*
  402.          * Perform expansions on the argument string.
  403.          */
  404.         if (ecp->ec_flags & EC_INTEXP) {
  405.             arg = expand_percents(arg);
  406.         }
  407.         if (ecp->ec_flags & EC_FILEXP) {
  408.             arg = fexpand(arg);
  409.         }
  410.  
  411.         if (ecp->ec_arg_type == ec_strings) {
  412.             makeargv(arg, &argc, &argv, " \t");
  413.         }
  414.         }
  415.     }
  416.     }
  417.  
  418.     savecho = echo;
  419.  
  420.     /*
  421.      * Now do the command.
  422.      */
  423.     switch (command) {
  424.     case EX_SHCMD:
  425.     /*
  426.      * If a line range was specified, this must be a pipe command.
  427.      * Otherwise, it's just a simple shell command.
  428.      */
  429.     if (l_line != NULL) {
  430.         specify_pipe_range(curwin, l_line, u_line);
  431.         do_pipe(curwin, arg);
  432.     } else {
  433.         do_shcmd(curwin, arg);
  434.     }
  435.     break;
  436.  
  437.     case EX_ARGS:
  438.     do_args(curwin);
  439.     break;
  440.  
  441.     case EX_BUFFER:
  442.     if (arg != NULL)
  443.         echo &= ~(e_SCROLL | e_REPORT | e_SHOWINFO);
  444.     (void) do_buffer(curwin, arg);
  445.     move_window_to_cursor(curwin);
  446.     update_window(curwin);
  447.     break;
  448.  
  449.     case EX_CHDIR:
  450.     {
  451.     char    *error;
  452.  
  453.     if ((error = do_chdir(arg)) != NULL) {
  454.         badcmd(interactive, error);
  455.     } else if (interactive) {
  456.         char    *dirp;
  457.  
  458.         if ((dirp = alloc(MAXPATHLEN + 2)) != NULL &&
  459.         getcwd(dirp, MAXPATHLEN + 2) != NULL) {
  460.         show_message(curwin, "%s", dirp);
  461.         }
  462.         if (dirp) {
  463.         free(dirp);
  464.         }
  465.     }
  466.     break;
  467.     }
  468.  
  469.     case EX_CLOSE:
  470.     do_close_window(curwin, exclam);
  471.     break;
  472.  
  473.     case EX_COMMENT:        /* This one is easy ... */
  474.     break;
  475.  
  476.     case EX_COMPARE:
  477.     do_compare();
  478.     break;
  479.  
  480.     case EX_COPY:
  481.     do_cdmy('c', l_line, u_line, a_line);
  482.     break;
  483.  
  484.     case EX_DELETE:
  485.     do_cdmy('d', l_line, u_line, (Line *) NULL);
  486.     break;
  487.  
  488.     case EX_ECHO:            /* echo arguments on command line */
  489.     {
  490.     int    i;
  491.  
  492.     flexclear(&curwin->w_statusline);
  493.     for (i = 0; i < argc; i++) {
  494.         if (!lformat(&curwin->w_statusline, "%s ", argv[i])
  495.             || flexlen(&curwin->w_statusline) >= curwin->w_ncols) {
  496.         break;
  497.         }
  498.     }
  499.     update_sline(curwin);
  500.     break;
  501.     }
  502.  
  503.     case EX_EDIT:
  504.     case EX_VISUAL:            /* treat :vi as :edit */
  505.     echo &= ~(e_SCROLL | e_REPORT | e_SHOWINFO);
  506.     (void) do_edit(curwin, exclam, arg);
  507.     move_window_to_cursor(curwin);
  508.     update_buffer(curbuf);
  509. #if 0
  510.     show_file_info(curwin);
  511. #endif
  512.     break;
  513.  
  514.     case EX_FILE:
  515.     if (arg != NULL) {
  516.         if (curbuf->b_filename != NULL)
  517.         free(curbuf->b_filename);
  518.         curbuf->b_filename = strsave(arg);
  519.         if (curbuf->b_tempfname != NULL) {
  520.         free(curbuf->b_tempfname);
  521.         curbuf->b_tempfname = NULL;
  522.         }
  523.     }
  524.     show_file_info(curwin);
  525.     break;
  526.  
  527.     case EX_GLOBAL:
  528.     do_global(curwin, l_line, u_line, arg, !exclam);
  529.     break;
  530.  
  531.     case EX_HELP:
  532.     do_help(curwin);
  533.     break;
  534.  
  535.     case EX_MAP:
  536.     xvi_map(argc, argv, exclam, interactive);
  537.     break;
  538.  
  539.     case EX_UNMAP:
  540.     xvi_unmap(argc, argv, exclam, interactive);
  541.     break;
  542.  
  543.     case EX_MARK:
  544.     case EX_K:
  545.     {
  546.     Posn    pos;
  547.  
  548.     pos.p_index = 0;
  549.     if (l_line == NULL) {
  550.         pos.p_line = curwin->w_cursor->p_line;
  551.     } else {
  552.         pos.p_line = l_line;
  553.     }
  554.     (void) setmark(arg[0], curbuf, &pos);
  555.     break;
  556.     }
  557.  
  558.     case EX_MOVE:
  559.     do_cdmy('m', l_line, u_line, a_line);
  560.     break;
  561.  
  562.     case EX_NEXT:
  563.     /*
  564.      * do_next() handles turning off the appropriate bits
  565.      * in echo, & also calls move_window_to_cursor() &
  566.      * update_buffer() as required, so we don't have to
  567.      * do any of that here.
  568.      */
  569.     do_next(curwin, argc, argv, exclam);
  570.     break;
  571.  
  572.     case EX_PRESERVE:
  573.     if (do_preserve())
  574.         show_file_info(curwin);
  575.     break;
  576.  
  577.     case EX_LIST:
  578.     case EX_PRINT:
  579.     case EX_NUMBER:
  580.     if (l_line == NULL) {
  581.         curline = curwin->w_cursor->p_line;
  582.         lastline = curline->l_next;
  583.     } else if (u_line ==  NULL) {
  584.         curline = l_line;
  585.         lastline = l_line->l_next;
  586.     } else {
  587.         curline = l_line;
  588.         lastline = u_line->l_next;
  589.     }
  590.     do_line_numbers = (Pb(P_number) || command == EX_NUMBER);
  591.     disp_init(curwin, show_line, (int) curwin->w_ncols,
  592.                 command == EX_LIST || Pb(P_list));
  593.     break;
  594.  
  595.     case EX_PUT:
  596.     {
  597.     Posn    where;
  598.  
  599.     if (l_line != NULL) {
  600.         where.p_index = 0;
  601.         where.p_line = l_line;
  602.     } else {
  603.         where.p_index = curwin->w_cursor->p_index;
  604.         where.p_line = curwin->w_cursor->p_line;
  605.     }
  606.     do_put(curwin, &where, FORWARD, '@');
  607.     break;
  608.     }
  609.  
  610.     case EX_QUIT:
  611.     do_quit(curwin, exclam);
  612.     break;
  613.  
  614.     case EX_REWIND:
  615.     do_rewind(curwin, exclam);
  616.     break;
  617.  
  618.     case EX_READ:
  619.     if (arg == NULL) {
  620.         badcmd(interactive, "Unrecognized command");
  621.         break;
  622.     }
  623.     do_read(curwin, arg, (l_line != NULL) ? l_line :
  624.                         curwin->w_cursor->p_line);
  625.     break;
  626.  
  627.     case EX_SET:
  628.     do_set(curwin, argc, argv, interactive);
  629.     break;
  630.  
  631.     case EX_SHELL:
  632.     do_shell(curwin);
  633.     break;
  634.  
  635.     case EX_SOURCE:
  636.     if (arg == NULL) {
  637.         badcmd(interactive, "Missing filename");
  638.     } else if (do_source(interactive, arg) && interactive) {
  639.         show_file_info(curwin);
  640.     }
  641.     break;
  642.  
  643.     case EX_SPLIT:
  644.     /*
  645.      * "split".
  646.      */
  647.     do_split_window(curwin);
  648.     break;
  649.  
  650.     case EX_SUBSTITUTE:
  651.     case EX_AMPERSAND:
  652.     case EX_TILDE:
  653.     {
  654.     long        nsubs;
  655.     register long    (*func) P((Xviwin *, Line *, Line *, char *));
  656.  
  657.     switch (command) {
  658.     case EX_SUBSTITUTE:
  659.         func = do_substitute;
  660.         break;
  661.     case EX_AMPERSAND:
  662.         func = do_ampersand;
  663.         break;
  664.     case EX_TILDE:
  665.         func = do_tilde;
  666.     }
  667.  
  668.     nsubs = (*func)(curwin, l_line, u_line, arg);
  669.     update_buffer(curbuf);
  670.     cursupdate(curwin);
  671.     begin_line(curwin, TRUE);
  672.     if (nsubs >= Pn(P_report)) {
  673.         show_message(curwin, "%ld substitution%c",
  674.             nsubs,
  675.             (nsubs > 1) ? 's' : ' ');
  676.     }
  677.     break;
  678.     }
  679.  
  680.     case EX_SUSPEND:
  681.     do_suspend(curwin);
  682.     break;
  683.  
  684.     case EX_TAG:
  685.     (void) do_tag(curwin, arg, exclam, TRUE, TRUE);
  686.     break;
  687.  
  688.     case EX_V:
  689.     do_global(curwin, l_line, u_line, arg, FALSE);
  690.     break;
  691.  
  692.     case EX_VERSION:
  693.     show_message(curwin, Version);
  694.     break;
  695.  
  696.     case EX_WN:
  697.     /*
  698.      * This is not a standard "vi" command, but it
  699.      * is provided in PC vi, and it's quite useful.
  700.      */
  701.     if (do_write(curwin, (char *) NULL, (Line *) NULL, (Line *) NULL,
  702.                                 exclam)) {
  703.         /*
  704.          * See comment for EX_NEXT (above).
  705.          */
  706.         do_next(curwin, 0, argv, exclam);
  707. #if 0
  708.         move_window_to_cursor(curwin);
  709.         update_buffer(curbuf);
  710. #endif
  711.     }
  712.     break;
  713.  
  714.     case EX_WQ:
  715.     do_wq(curwin, arg, exclam);
  716.     break;
  717.  
  718.     case EX_WRITE:
  719.     (void) do_write(curwin, arg, l_line, u_line, exclam);
  720.     break;
  721.  
  722.     case EX_XIT:
  723.     do_xit(curwin);
  724.     break;
  725.  
  726.     case EX_YANK:
  727.     do_cdmy('y', l_line, u_line, (Line *) NULL);
  728.     break;
  729.  
  730.     case EX_EXBUFFER:
  731.     yp_stuff_input(curwin, arg[0], FALSE);
  732.     break;
  733.  
  734.     case EX_EQUALS:
  735.     do_equals(curwin, l_line);
  736.     break;
  737.  
  738.     case EX_LSHIFT:
  739.     case EX_RSHIFT:
  740.     if (l_line == NULL) {
  741.         l_line = curwin->w_cursor->p_line;
  742.     }
  743.     if (u_line == NULL) {
  744.         u_line = l_line;
  745.     }
  746.     tabinout((command == EX_LSHIFT) ? '<' : '>', l_line, u_line);
  747.     begin_line(curwin, TRUE);
  748.     update_buffer(curbuf);
  749.     break;
  750.  
  751.     case EX_NOCMD:
  752.     /*
  753.      * If we got a line, but no command, then go to the line.
  754.      */
  755.     if (l_line != NULL) {
  756.         if (l_line == curbuf->b_line0) {
  757.         l_line = l_line->l_next;
  758.         }
  759.         move_cursor(curwin, l_line, 0);
  760.         begin_line(curwin, TRUE);
  761.     }
  762.     break;
  763.  
  764.     case EX_ENOTFOUND:
  765.     badcmd(interactive, "Unrecognized command");
  766.     break;
  767.  
  768.     case EX_EAMBIGUOUS:
  769.     badcmd(interactive, "Ambiguous command");
  770.     break;
  771.  
  772.     case EX_ECANTFORCE:
  773.     badcmd(interactive, "Inappropriate use of !");
  774.     break;
  775.  
  776.     case EX_EBADARGS:
  777.     badcmd(interactive, "Inappropriate arguments given");
  778.     break;
  779.  
  780.     default:
  781.     /*
  782.      * Decoded successfully, but unknown to us. Whoops!
  783.      */
  784.     badcmd(interactive, "Internal error - unimplemented command.");
  785.     break;
  786.  
  787.     case EX_ABBREVIATE:
  788.     case EX_APPEND:
  789.     case EX_CHANGE:
  790.     case EX_EX:
  791.     case EX_GOTO:
  792.     case EX_INSERT:
  793.     case EX_JOIN:
  794.     case EX_OPEN:
  795.     case EX_RECOVER:
  796.     case EX_UNABBREV:
  797.     case EX_UNDO:
  798.     case EX_Z:
  799.     badcmd(interactive, "Unimplemented command.");
  800.     break;
  801.     }
  802.  
  803.     echo = savecho;
  804.  
  805.     if (argc > 0 && argv != NULL) {
  806.     free((char *) argv);
  807.     }
  808. }
  809.  
  810. /*
  811.  * Find the correct command for the given string, and return it.
  812.  *
  813.  * Updates string pointer to point to 1st char after command.
  814.  *
  815.  * Updates boolean pointed to by forcep according
  816.  * to whether an '!' was given after the command;
  817.  * if an '!' is given for a command which can't take it,
  818.  * this is an error, and EX_ECANTFORCE is returned.
  819.  * For unknown commands, EX_ENOTFOUND is returned.
  820.  * For ambiguous commands, EX_EAMBIGUOUS is returned.
  821.  *
  822.  * Also updates *cmdp to point at entry in command table.
  823.  */
  824. static int
  825. decode_command(str, forcep, cmdp)
  826. char        **str;
  827. bool_t        *forcep;
  828. struct    ecmd    **cmdp;
  829. {
  830.     struct ecmd    *cmdptr;
  831.     struct ecmd    *last_match = NULL;
  832.     bool_t    last_exclam = FALSE;
  833.     int        nmatches = 0;
  834.     char    *last_delimiter = *str;
  835.  
  836.     for (cmdptr = cmdtable; cmdptr->ec_name != NULL; cmdptr++) {
  837.     char    *name = cmdptr->ec_name;
  838.     char    *strp;
  839.     bool_t    matched;
  840.  
  841.     strp = *str;
  842.  
  843.     while (*strp == *name && *strp != '\0') {
  844.         strp++;
  845.         name++;
  846.     }
  847.  
  848.     matched = (
  849.         /*
  850.          * we've got a full match, or ...
  851.          */
  852.         *strp == '\0'
  853.         ||
  854.         /*
  855.          * ... a whitespace delimiter, or ...
  856.          */
  857.         is_space(*strp)
  858.         ||
  859.         (
  860.         /*
  861.          * ... at least one character has been
  862.          * matched, and ...
  863.          */
  864.         name > cmdptr->ec_name
  865.         &&
  866.         (
  867.             (
  868.             /*
  869.              * ... this command can accept a
  870.              * '!' qualifier, and we've found
  871.              * one ...
  872.              */
  873.             (cmdptr->ec_flags & EC_EXCLAM)
  874.             &&
  875.             *strp == '!'
  876.             )
  877.             ||
  878.             (
  879.             /*
  880.              * ... or it can take a
  881.              * non-alphanumeric delimiter
  882.              * (like '/') ...
  883.              */
  884.             cmdptr->ec_arg_type == ec_nonalnum
  885.             &&
  886.             !is_alnum(*strp)
  887.             )
  888.             ||
  889.             (
  890.             /*
  891.              * ... or it doesn't need any
  892.              * delimiter ...
  893.              */
  894.             cmdptr->ec_arg_type == ec_rest
  895.             )
  896.             ||
  897.             (
  898.             /*
  899.              * ... or we've got a full match,
  900.              * & the command expects a single
  901.              * lower-case letter as an
  902.              * argument, and we've got one ...
  903.              */
  904.             cmdptr->ec_arg_type == ec_1lower
  905.             &&
  906.             *name == '\0'
  907.             &&
  908.             is_lower(*strp)
  909.             )
  910.             ||
  911.             (
  912.             /*
  913.              * ... or we've got a partial match,
  914.              * and the command expects a line
  915.              * specifier as an argument, and the
  916.              * next character is not alphabetic.
  917.              */
  918.             cmdptr->ec_arg_type == ec_line
  919.             &&
  920.             !is_alpha(*strp)
  921.             )
  922.         )
  923.         )
  924.     );
  925.  
  926.     if (!matched)
  927.         continue;
  928.  
  929.     if (last_match == NULL ||
  930.         (last_match != NULL &&
  931.          last_match->ec_priority < cmdptr->ec_priority)) {
  932.         /*
  933.          * No previous match, or this one is better.
  934.          */
  935.         last_match = cmdptr;
  936.         last_exclam = (*strp == '!');
  937.         last_delimiter = strp;
  938.         nmatches = 1;
  939.  
  940.         /*
  941.          * If this is a complete match, we don't
  942.          * need to search the rest of the table.
  943.          */
  944.         if (*name == '\0')
  945.         break;
  946.  
  947.     } else if (last_match != NULL &&
  948.            last_match->ec_priority == cmdptr->ec_priority) {
  949.         /*
  950.          * Another match with the same priority - remember it.
  951.          */
  952.         nmatches++;
  953.     }
  954.     }
  955.  
  956.     /*
  957.      * For us to have found a good match, there must have been
  958.      * exactly one match at a certain priority, and if an '!'
  959.      * was used, it must be allowed by that match.
  960.      */
  961.     if (last_match == NULL) {
  962.     return(EX_ENOTFOUND);
  963.     } else if (nmatches != 1) {
  964.     return(EX_EAMBIGUOUS);
  965.     } else if (last_exclam && ! (last_match->ec_flags & EC_EXCLAM)) {
  966.     return(EX_ECANTFORCE);
  967.     } else {
  968.     *forcep = last_exclam;
  969.     *str = last_delimiter;
  970.     *cmdp = last_match;
  971.     return(last_match->ec_command);
  972.     }
  973. }
  974.  
  975. /*
  976.  * get_range - parse a range specifier
  977.  *
  978.  * Ranges are of the form
  979.  *
  980.  * ADDRESS1,ADDRESS2
  981.  * ADDRESS        (equivalent to "ADDRESS,ADDRESS")
  982.  * ADDRESS,        (equivalent to "ADDRESS,.")
  983.  * ,ADDRESS        (equivalent to ".,ADDRESS")
  984.  * ,            (equivalent to ".")
  985.  * %            (equivalent to "1,$")
  986.  *
  987.  * where ADDRESS is
  988.  *
  989.  * /regular expression/ [INCREMENT]
  990.  * ?regular expression? [INCREMENT]
  991.  * $   [INCREMENT]
  992.  * 'x  [INCREMENT]    (where x denotes a currently defined mark)
  993.  * .   [INCREMENT]
  994.  * INCREMENT        (equivalent to . INCREMENT)
  995.  * number [INCREMENT]
  996.  *
  997.  * and INCREMENT is
  998.  *
  999.  * + number
  1000.  * - number
  1001.  * +            (equivalent to +1)
  1002.  * -            (equivalent to -1)
  1003.  * ++            (equivalent to +2)
  1004.  * --            (equivalent to -2)
  1005.  *
  1006.  * etc.
  1007.  *
  1008.  * The pointer *cpp is updated to point to the first character following
  1009.  * the range spec. If an initial address is found, but no second, the
  1010.  * upper bound is equal to the lower, except if it is followed by ','.
  1011.  *
  1012.  * Return FALSE if an error occurred, otherwise TRUE.
  1013.  */
  1014. static bool_t
  1015. get_range(cpp)
  1016. register char    **cpp;
  1017. {
  1018.     register char    *cp;
  1019.     char        sepchar;
  1020.  
  1021. #define skipp    { cp = *cpp; skipblanks(cp); *cpp = cp; }
  1022.  
  1023.     skipp;
  1024.  
  1025.     if (*cp == '%') {
  1026.     /*
  1027.      * "%" is the same as "1,$".
  1028.      */
  1029.     l_line = curbuf->b_file;
  1030.     u_line = curbuf->b_lastline->l_prev;
  1031.     ++*cpp;
  1032.     return TRUE;
  1033.     }
  1034.  
  1035.     if (!get_line(cpp, &l_line)) {
  1036.     return FALSE;
  1037.     }
  1038.  
  1039.     skipp;
  1040.  
  1041.     sepchar = *cp;
  1042.     if (sepchar != ',' && sepchar != ';') {
  1043.     u_line = l_line;
  1044.     return TRUE;
  1045.     }
  1046.  
  1047.     ++*cpp;
  1048.  
  1049.     if (l_line == NULL) {
  1050.     /*
  1051.      * So far, we've got ",".
  1052.      *
  1053.      * ",address" is equivalent to ".,address".
  1054.      */
  1055.     l_line = curwin->w_cursor->p_line;
  1056.     }
  1057.  
  1058.     if (sepchar == ';') {
  1059.     move_cursor(curwin, (l_line != curbuf->b_line0) ?
  1060.             l_line : l_line->l_next, 0);
  1061.     }
  1062.  
  1063.     skipp;
  1064.     if (!get_line(cpp, &u_line)) {
  1065.     return FALSE;
  1066.     }
  1067.     if (u_line == NULL) {
  1068.     /*
  1069.      * "address," is equivalent to "address,.".
  1070.      */
  1071.     u_line = curwin->w_cursor->p_line;
  1072.     }
  1073.  
  1074.     /*
  1075.      * Check for reverse ordering.
  1076.      */
  1077.     if (earlier(u_line, l_line)) {
  1078.     Line    *tmp;
  1079.  
  1080.     tmp = l_line;
  1081.     l_line = u_line;
  1082.     u_line = tmp;
  1083.     }
  1084.  
  1085.     skipp;
  1086.     return TRUE;
  1087. }
  1088.  
  1089. static bool_t
  1090. get_line(cpp, lpp)
  1091.     char        **cpp;
  1092.     Line        **lpp;
  1093. {
  1094.     Line        *pos;
  1095.     char        *cp;
  1096.     char        c;
  1097.     long        lnum;
  1098.  
  1099.     cp = *cpp;
  1100.  
  1101.     /*
  1102.      * Determine the basic form, if present.
  1103.      */
  1104.     switch (c = *cp++) {
  1105.  
  1106.     case '/':
  1107.     case '?':
  1108.     pos = linesearch(curwin, (c == '/') ? FORWARD : BACKWARD,
  1109.                     &cp);
  1110.     if (pos == NULL) {
  1111.         return FALSE;
  1112.     }
  1113.     break;
  1114.  
  1115.     case '$':
  1116.     pos = curbuf->b_lastline->l_prev;
  1117.     break;
  1118.  
  1119.     /*
  1120.      * "+n" is equivalent to ".+n".
  1121.      */
  1122.     case '+':
  1123.     case '-':
  1124.     cp--;
  1125.      /* fall through ... */
  1126.     case '.':
  1127.     pos = curwin->w_cursor->p_line;
  1128.     break;
  1129.  
  1130.     case '\'': {
  1131.     Posn        *lp;
  1132.  
  1133.     lp = getmark(*cp++, curbuf);
  1134.     if (lp == NULL) {
  1135.         show_error(curwin, "Unknown mark");
  1136.         return FALSE;
  1137.     }
  1138.     pos = lp->p_line;
  1139.     break;
  1140.     }
  1141.     case '0': case '1': case '2': case '3': case '4':
  1142.     case '5': case '6': case '7': case '8': case '9':
  1143.     for (lnum = c - '0'; is_digit(*cp); cp++)
  1144.         lnum = lnum * 10 + *cp - '0';
  1145.  
  1146.     if (lnum == 0) {
  1147.         pos = curbuf->b_line0;
  1148.     } else {
  1149.         pos = gotoline(curbuf, lnum);
  1150.     }
  1151.     break;
  1152.     
  1153.     default:
  1154.     return TRUE;
  1155.     }
  1156.  
  1157.     skipblanks(cp);
  1158.  
  1159.     if (*cp == '-' || *cp == '+') {
  1160.     char    dirchar = *cp++;
  1161.  
  1162.     skipblanks(cp);
  1163.     if (is_digit(*cp)) {
  1164.         for (lnum = 0; is_digit(*cp); cp++) {
  1165.         lnum = lnum * 10 + *cp - '0';
  1166.         }
  1167.     } else {
  1168.         for (lnum = 1; *cp == dirchar; cp++) {
  1169.         lnum++;
  1170.         }
  1171.     }
  1172.  
  1173.     if (dirchar == '-')
  1174.         lnum = -lnum;
  1175.  
  1176.     pos = gotoline(curbuf, lineno(curbuf, pos) + lnum);
  1177.     }
  1178.  
  1179.     *cpp = cp;
  1180.     *lpp = pos;
  1181.     return TRUE;
  1182. }
  1183.  
  1184. /*
  1185.  * This routine is called for ambiguous, unknown,
  1186.  * badly defined or unimplemented commands.
  1187.  */
  1188. static void
  1189. badcmd(interactive, str)
  1190. bool_t    interactive;
  1191. char    *str;
  1192. {
  1193.     if (interactive) {
  1194.     show_error(curwin, str);
  1195.     }
  1196. }
  1197.  
  1198. static char *
  1199. show_line()
  1200. {
  1201.     Line    *lp;
  1202.  
  1203.     if (curline == lastline) {
  1204.     return(NULL);
  1205.     }
  1206.  
  1207.     lp = curline;
  1208.     curline = curline->l_next;
  1209.  
  1210.     if (do_line_numbers) {
  1211.     static Flexbuf    nu_line;
  1212.  
  1213.     flexclear(&nu_line);
  1214.     (void) lformat(&nu_line, NUM_FMT, lineno(curbuf, lp));
  1215.     (void) lformat(&nu_line, "%s", lp->l_text);
  1216.     return flexgetstr(&nu_line);
  1217.     } else {
  1218.     return(lp->l_text);
  1219.     }
  1220. }
  1221.  
  1222. static char *
  1223. expand_percents(str)
  1224. char    *str;
  1225. {
  1226.     static Flexbuf    newstr;
  1227.     register char    *from;
  1228.     register bool_t    escaped;
  1229.  
  1230.     if (strpbrk(str, "%#") == (char *) NULL) {
  1231.     return str;
  1232.     }
  1233.     flexclear(&newstr);
  1234.     escaped = FALSE;
  1235.     for (from = str; *from != '\0'; from++) {
  1236.     if (!escaped) {
  1237.         if (*from == '%' && curbuf->b_filename != NULL) {
  1238.         (void) lformat(&newstr, "%s", curbuf->b_filename);
  1239.         } else if (*from == '#' && altfilename != NULL) {
  1240.         (void) lformat(&newstr, "%s", altfilename);
  1241.         } else if (*from == '\\') {
  1242.         escaped = TRUE;
  1243.         } else {
  1244.         (void) flexaddch(&newstr, *from);
  1245.         }
  1246.     } else {
  1247.         if (*from != '%' && *from != '#') {
  1248.         (void) flexaddch(&newstr, '\\');
  1249.         }
  1250.         (void) flexaddch(&newstr, *from);
  1251.         escaped = FALSE;
  1252.     }
  1253.     }
  1254.     return flexgetstr(&newstr);
  1255. }
  1256.